package com.youlu.campus.admin.auth.shiro.filter;

import com.alibaba.fastjson.JSON;
import com.youlu.campus.admin.auth.entity.exception.AuthErrorCode;
import com.youlu.campus.admin.auth.shiro.exception.AuthException;
import com.youlu.campus.admin.auth.shiro.token.AuthToken;
import com.youlu.campus.admin.auth.shiro.token.AuthType;
import com.youlu.campus.common.exception.ApiErrorCode;
import com.youlu.campus.common.exception.BusinessException;
import com.youlu.campus.common.utils.ExceptionUtils;
import org.apache.commons.codec.CharEncoding;
import org.apache.commons.codec.net.URLCodec;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.util.Objects;

/**
 * Created by zhuhuaiqi
 *
 * @author zhuhuaiqi
 */
public class AuthenticationFilter extends AccessControlFilter {
    protected final Logger logger = LoggerFactory.getLogger(this.getClass());
    public static final String DEFAULT_SUCCESS_URL = "/";

    public static final String DEFAULT_ERROR_MESSAGE_PARAM = "error";

    public static final String ATTR_IS_LOGIN_SUBMISSION = "_isLoginSubmission";

    private static final String AUTH_EXCEPTION = "_authException";

    public static final String DEFAULT_ACCESS_TOKEN = "access_token";


    protected final URLCodec urlCodec = new URLCodec();

    private String successUrl = DEFAULT_SUCCESS_URL;

    private String errorMessageParam = DEFAULT_ERROR_MESSAGE_PARAM;

    private String accessToken = DEFAULT_ACCESS_TOKEN;

    private Cookie sessionIdCookie;

    private Cookie usernameCookie;

    private Cookie userIdCookie;

    private Cookie comIdCookie;

    private Cookie domainCookie;

    /**
     * 是否更新ttl
     */
    private boolean updateTtl = true;

    private String defaultDomain="yiyzu.net";


    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object
            mappedValue) throws Exception {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String referer = httpRequest.getHeader("Referer");
        String reqDate = httpRequest.getHeader("Date");
//        logger.info(":>>> AuthenticationFilter.isAccessAllowed URL:{}, URI:{}, method:{}, " +
//                        "queryString:{}, referer:{}, Date:{}",
//                httpRequest.getRequestURL(), httpRequest.getRequestURI(), httpRequest.getMethod(),
//                httpRequest.getQueryString(), referer, reqDate);

        //登录请求就不
        if (isLoginRequest(request, response)) {
//            logger.debug(":>>> 登陆请求,AuthenticationFilter.isAccessAllowed isLoginRequest true");
            if (isLoginSubmission(request, response)) {
              request.setAttribute(ATTR_IS_LOGIN_SUBMISSION, true);
                return isCustomLogin();
            }
            return true;
        }
        return validCredentials(request, response);
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws
            Exception {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        /*logger.debug("AuthenticationFilter onAccessDenied method:{}, queryString:{}, URI:{}, " +
                        "URL:{}, parameters:{}",
                httpRequest.getMethod(),
                httpRequest.getQueryString(), httpRequest.getRequestURI(), httpRequest
                        .getRequestURL(), JSON.toJSONString(httpRequest.getParameterMap()));*/

        //logger.info("onAccessDenied executeLogin");
        Object attribute =  request.getAttribute(ATTR_IS_LOGIN_SUBMISSION);
//        logger.debug(":>>> AuthenticationFilter.onAccessDenied _isLoginSubmission rquest attribute:{}",
//                JSON.toJSONString(attribute));

        boolean result = false;
        if (attribute != null && ((boolean) attribute)) {
//            logger.debug(":>>> AuthenticationFilter.onAccessDenied executeLogin");
            result = executeLogin(request, response);
        }
        if (!result) {
            redirectToLogin(request, response);
        }
        return false;
    }

    @Override
    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws
            IOException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;

       /* logger.debug("redirectToLogin method:{}, queryString:{}, URI:{}, URL:{}, parameters:{}",
                httpRequest
                        .getMethod(),
                httpRequest.getQueryString(), httpRequest.getRequestURI(), httpRequest
                        .getRequestURL(), httpRequest.getParameterMap());*/

        ApiErrorCode errorCode = AuthErrorCode.USER_NOT_LOGIN;

        Object error = request.getAttribute(AUTH_EXCEPTION);
//        logger.debug("AuthenticationFilter.redirectToLogin error:{}", JSON.toJSONString(error));
        if (error != null) {
            errorCode = (ApiErrorCode) error;
            logger.info("AuthenticationFilter.redirectToLogin auth exception code:{}, message:{}",
                    errorCode.getCode(), errorCode.getMessage());
        }
        if (isAjaxRequest(request)) {
            com.youlu.campus.admin.utils.WebUtils.outJSON(response, errorCode);
            return;
        }
        boolean isLoginSubmission = false;
        Object attribute =  request.getAttribute(ATTR_IS_LOGIN_SUBMISSION);
        if (attribute != null) {
            isLoginSubmission = (boolean) attribute;
        }
//        logger.debug(":>> AuthenticationFilter.redirectToLogin attribute isLoginSubmission:{}", isLoginSubmission);

        StringBuilder loginUrl = new StringBuilder(getLoginUrl());
        logger.debug(":>>> AuthenticationFilter.redirectToLogin loginUrl:{}", loginUrl);

        StringBuilder targetUrl = new StringBuilder(httpRequest.getRequestURI());
        if (GET_METHOD.equalsIgnoreCase(httpRequest.getMethod())) {
            String queryString = httpRequest.getQueryString();
            if (StringUtils.hasLength(queryString)) {
                targetUrl.append("?").append(queryString);
            }
//            logger.debug(":>>> targetUrl :{}", targetUrl.toString());
            String targetUrlStr = urlCodec.encode(targetUrl.toString(), CharEncoding.UTF_8);
            loginUrl.append(":>>> &targetUrl=").append(targetUrlStr);
        }
        if (isLoginSubmission) {
            Object errorObj = request.getAttribute(getErrorMessageParam());
            if (errorObj != null) {
                String errorMessage = (String) errorObj;
//                logger.debug(":>>> isLoginSubmission:{} error:{}", isLoginSubmission, errorMessage);
                loginUrl.append("&").append(getErrorMessageParam()).append("=").append(urlCodec
                        .encode(string2Unicode
                                (errorMessage), CharEncoding.UTF_8));
            }
        }
        logger.debug(":>>> redirectToLogin loginUrl:{}", loginUrl.toString());
        if (loginUrl.indexOf("?") == -1) {
            int len = getLoginUrl().length();
            loginUrl.replace(len, len + 1, "?");
        }
//        logger.info(":>>> AuthenticationFilter.redirectToLogin loginUrl:{}", loginUrl.toString());
        WebUtils.issueRedirect(request, response, loginUrl.toString());
    }

    /**
     * 是否自定义登录来实现登录操作
     *
     * @return
     */
    protected boolean isCustomLogin() {
        logger.info(":>>> 是否自定义登录来实现登录操作 false");
        return false;
    }

    protected boolean validCredentials(ServletRequest request, ServletResponse response) throws
            Exception {
        ApiErrorCode errorCode = AuthErrorCode.USER_NOT_LOGIN;
        try {
            AuthToken credentials = createAccessCredentials(request, response);
            if (credentials == null) {
                return false;
            }
            Subject subject = getSubject(request, response);
            logger.info(":>>> AuthenticationFilter.validCredentials isAuthenticated:{}, credentials:{}",
                    subject.isAuthenticated(), JSON.toJSONString(credentials));
            //未通过认证,则需要进行认证
            if (!subject.isAuthenticated()) {
                //subject.login(credentials);
            } else {
                logger.info(":>>> uthenticationFilter.validCredentials isAuthenticated true, Principals:{}",
                        JSON.toJSONString(subject.getPrincipals()));
            }

            subject.login(credentials);

            //String asserss_token = String.valueOf(credentials.getPassword());
            //logger.debug("validCredentials succ storre :{}", asserss_token);
            //再次重新写入过期时间
            //storeAccessToken(asserss_token, (HttpServletRequest) request, (HttpServletResponse)
            // response);
            return true;
        } catch (BusinessException be) {
            logger.warn(":>>> AuthenticationFilter.validCredentials warn ex code:{}, message:{}",
                    be.getMessage());
            ExceptionUtils.business(be.getError());
        } catch (Exception ex) {
            logger.debug("validCredentials result:{}",  ex instanceof AuthException);
            if (ex instanceof AuthException) {
                AuthException aex = (AuthException) ex;
                errorCode = aex.getErrorCode();
                //logger.debug("validCredentials ex :{}", aex);
            }
            //logger.warn("validCredentials failure:{}", ex.getMessage());
        }
        request.setAttribute(AUTH_EXCEPTION, errorCode);
        return false;
    }

    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws
            Exception {
        return true;
    }

    protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
        return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod()
                .equalsIgnoreCase(POST_METHOD);
    }

    protected AuthToken createAccessCredentials(ServletRequest request, ServletResponse
            response)
            throws Exception {
        Serializable accessCredentials = getAccessCredentials(request, response);
        String username = getUsername(request, response);
        String domain = getDomain(request, response);
//        logger.debug(":>>> AuthenticationFilter.createAccessCredentials username:{}, domain:{}, accessCredentials:{}",
//                username, domain, String.valueOf(accessCredentials));
        if (accessCredentials == null) {
            logger.warn("AuthenticationFilter.createAccessCredentials accessCredentials is null");
            throw new AuthException(AuthErrorCode.USER_NOT_LOGIN);
            //ExceptionUtils.business(AuthErrorCode.USER_NOT_LOGIN);
        }
        //logger.debug("createAccessCredentials :{}", JSON.toJSONString(accessCredentials));
        return new AuthToken(username, accessCredentials
                .toString(), domain, AuthType.ACCESS_CREDENTIALS, updateTtl);
    }

    protected void storeAccessToken(Serializable serializable, HttpServletRequest request,
                                    HttpServletResponse response) {
        Cookie cookie = getSessionIdCookie();
        String idString = serializable.toString();
        cookie.setValue(idString);
        cookie.saveTo(request, response);
        logger.info("Set access token cookie for session with {}", JSON.toJSONString(cookie));
    }

    protected void removeAccessToken(HttpServletRequest request, HttpServletResponse response) {
        logger.debug("removeAccessToken getSessionIdCookie");
        getSessionIdCookie().removeFrom(request, response);
    }

    protected Serializable getAccessCredentials(ServletRequest request, ServletResponse response) {
        Serializable accessCredentials = getReferencedAccessCredentials(request, response);
        logger.debug(":>>> 访问密钥:{}", JSON.toJSONString(accessCredentials));
        return accessCredentials;
    }

    protected String getUsername(ServletRequest request, ServletResponse response) {
        String username = getUsernameCookieValue(request, response);
        logger.info(":>>> 从cookie 中读取用户名:{}", username);
        return username;
//      StringUtils.trimToEmpty(username);
    }

    protected String getDomain(ServletRequest request, ServletResponse response) {

        String domain = getDomainCookieValue(request, response);
        logger.info(":>>> 从cookie 中 domain:{}", domain);
        return domain;
    }

    protected boolean isAjaxRequest(ServletRequest request) {
        /*
        if (!(request instanceof HttpServletRequest)) {
            logger.debug("Current request is not an HttpServletRequest - cannot get cookie.  " +
                    "Returning null.");
            return Boolean.FALSE;
        }
        String header = ((HttpServletRequest) request).getHeader("X-Requested-With");
        logger.debug("isAjaxRequest header:{}", JSON.toJSONString(header));
        if (header != null && "XMLHttpRequest".equalsIgnoreCase(header)) {
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
        */
        return Boolean.TRUE;
    }

    private String getAccessCredentialsCookieValue(ServletRequest request, ServletResponse
            response) {
        if (!(request instanceof HttpServletRequest)) {
            logger.debug("Current request is not an HttpServletRequest - cannot get cookie.  " +
                    "Returning null.");
            return null;
        }
        HttpServletRequest httpRequest = (HttpServletRequest) request;
//        logger.debug("getAccessCredentialsCookieValue :{}", JSON.toJSONString
//                (getSessionIdCookie()));
        return getSessionIdCookie().readValue(httpRequest, WebUtils.toHttp(response));
    }

    private String getUsernameCookieValue(ServletRequest request, ServletResponse
            response) {
        if (!(request instanceof HttpServletRequest)) {
            logger.debug(":>>> Current request is not an HttpServletRequest - cannot get cookie.  " +
                    "Returning null.");
            return null;
        }
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String userName = getUsernameCookie().readValue(httpRequest, WebUtils.toHttp(response));
        if (Objects.isNull(userName)) {
            userName = ((HttpServletRequest) request).getHeader("userName");
            logger.info(":>>> 用户名为:{}", userName);
            if (Objects.isNull(userName)) {
                userName = request.getParameter("userName");
            }
        }
        logger.info(":>>> 获取的用户名:{}", userName);
        return userName;
    }

    private String getDomainCookieValue(ServletRequest request, ServletResponse
            response) {
        if (!(request instanceof HttpServletRequest)) {
            logger.debug("Current request is not an HttpServletRequest - cannot get cookie.  " +
                    "Returning null.");
            return null;
        }
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        String domain = getDomainCookie().readValue(httpRequest, WebUtils.toHttp(response));
        if (Objects.isNull(domain)) {
            domain = defaultDomain;
        }
        return domain;
    }

    private Serializable getReferencedAccessCredentials(ServletRequest request, ServletResponse
            response) {
        String value = getAccessCredentialsCookieValue(request, response);
        logger.info(":>>> 从cookie 中读取,getReferencedAccessCredentials cookie value:{}", value);
        if (value == null) {
            value = getUriPathSegmentParamValue(request, getAccessToken());
            logger.info(":>>> 从URI 中读取,getReferencedAccessCredentials cookie value:{}", value);
            if (value == null) {
                //not a URI path segment parameter, try the query parameters:
                value = request.getParameter(getAccessToken());
                logger.info(":>>> 从请求参数中读取,getReferencedAccessCredentials cookie value:{}", value);
                if (value == null) {
                    value = request.getParameter(getAccessToken());
                    value = ((HttpServletRequest) request).getHeader("password");
//                    userName = request.getParameter("userName");
                    value = ((HttpServletRequest) request).getParameter("password");
                    logger.info(":>>> 从Header中读取,getReferencedAccessCredentials cookie value:{}", value);
                }
            }
        }
//        logger.info(":>>> getReferencedAccessCredentials:{}", value);
        return value;
    }

    private String getUriPathSegmentParamValue(ServletRequest servletRequest, String paramName) {

        if (!(servletRequest instanceof HttpServletRequest)) {
            return null;
        }
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String uri = request.getRequestURI();
        if (uri == null) {
            return null;
        }

        int queryStartIndex = uri.indexOf('?');
        if (queryStartIndex >= 0) {
            //get rid of the query string
            uri = uri.substring(0, queryStartIndex);
        }

        int index = uri.indexOf(';'); //now check for path segment parameters:
        if (index < 0) {
            //no path segment params - return:
            return null;
        }

        //there are path segment params, let's get the last one that may exist:

        final String TOKEN = paramName + "=";

        uri = uri.substring(index + 1); //uri now contains only the path segment params

        //we only care about the last JSESSIONID param:
        index = uri.lastIndexOf(TOKEN);
        if (index < 0) {
            //no segment param:
            return null;
        }
        uri = uri.substring(index + TOKEN.length());
        index = uri.indexOf(';');
        //strip off any remaining segment params:
        if (index >= 0) {
            uri = uri.substring(0, index);
        }
        //what remains is the value
        return uri;
    }

    /**
     * 字符串转换unicode
     *
     * @param string
     * @return
     */
    protected String string2Unicode(String string) {
        StringBuilder unicode = new StringBuilder();
        for (int i = 0; i < string.length(); i++) {
            // 取出每一个字符
            char c = string.charAt(i);
            // 转换为unicode
            unicode.append("\\u" + Integer.toHexString(c));
        }
        return unicode.toString();
    }

    public String getSuccessUrl() {
        return successUrl;
    }

    public void setSuccessUrl(String successUrl) {
        //logger.debug("=================>>>>>>>>" + successUrl);
        successUrl = successUrl;
    }

    public String getErrorMessageParam() {
        return errorMessageParam;
    }

    public void setErrorMessageParam(String errorMessageParam) {
        this.errorMessageParam = errorMessageParam;
    }

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public Cookie getSessionIdCookie() {
        return sessionIdCookie;
    }

    public void setSessionIdCookie(Cookie sessionIdCookie) {
        this.sessionIdCookie = sessionIdCookie;
    }

    public Cookie getUsernameCookie() {
        return usernameCookie;
    }

    public void setUsernameCookie(Cookie usernameCookie) {
        this.usernameCookie = usernameCookie;
    }

    public Cookie getUserIdCookie() {
        return userIdCookie;
    }

    public void setUserIdCookie(Cookie userIdCookie) {
        this.userIdCookie = userIdCookie;
    }

    public Cookie getComIdCookie() {
        return comIdCookie;
    }

    public void setComIdCookie(Cookie comIdCookie) {
        this.comIdCookie = comIdCookie;
    }

    public Cookie getDomainCookie() {
        return domainCookie;
    }

    public void setDomainCookie(Cookie domainCookie) {
        this.domainCookie = domainCookie;
    }


    public boolean isUpdateTtl() {
        return updateTtl;
    }

    public void setUpdateTtl(boolean updateTtl) {
        this.updateTtl = updateTtl;
    }

}
